//
//  ClipMoreImageController.swift
//  ClipImageBrowser
//
//  Created by 好未来山神 on 2020/12/14.
//

import UIKit

open class ClipMoreImageController: UIViewController, UIGestureRecognizerDelegate {
    
    public var bottomViewHeight: CGFloat = 90
    public var leftMargin: CGFloat = 0
    public var closeButtonTopMargin: CGFloat = 4
    public var topMargin: CGFloat = 20
    public var closeButtonSize: CGSize = CGSize(width: 32, height: 32)
    public var minClipSize = CGSize(width: 40, height: 40)
    
    private let screenWidth = UIScreen.main.bounds.width
    private let screenHeight = UIScreen.main.bounds.height
    private let insetWidth: CGFloat = 20
    
    private let originalImage: UIImage
    private let originBoxFrames: [CGRect]
    
    private var editImage: UIImage
    
    private var beginPanPoint: CGPoint = .zero
    private var clipBoxFrame: CGRect = .zero
    private var clipOriginFrame: CGRect = .zero
    private var isRotating = false
    private var panEdge: ClipImageBrowserController.ClipPanEdge = .none
    
    private var safeAreaInsets: UIEdgeInsets {
        return UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero
    }
    
    private var maxContainerViewSize: CGSize {
        return CGSize(width: screenWidth - 2 * leftMargin, height: screenHeight - bottomViewHeight - safeAreaInsets.top - topMargin)
    }
    
    private var clipBoxDefaultSize: CGSize {
        return CGSize(width: previewSize.width, height: min(160, previewSize.height))
    }
    
    lazy var maxClipFrame: CGRect = {
        return CGRect(origin: CGPoint(x: leftMargin, y: safeAreaInsets.top - topMargin), size: maxContainerViewSize)
    }()
    
    private var previewSize: CGSize {
        var width: CGFloat = 1
        var height: CGFloat = 1
        let imageWidth = editImage.size.width
        let imageHeight = editImage.size.height
        let widthSpace = fabsf(Float(maxImageWidth - imageWidth))
        let heightSpace = fabsf(Float(maxImageHeight - imageHeight))
        if widthSpace >= heightSpace { // 宽图
            if maxImageWidth > imageWidth {
                width = imageWidth * (maxImageHeight / imageHeight)
                height = imageHeight * (maxImageHeight / imageHeight)
            } else {
                width = maxImageWidth
                height = maxImageWidth * (imageHeight / imageWidth)
            }
        } else {
            if maxImageHeight > imageHeight { // 竖长图
                width = imageWidth * (maxImageWidth / imageWidth)
                height = imageHeight * (maxImageWidth / imageWidth)
            } else {
                width = maxImageHeight * (imageWidth / imageHeight)
                height = maxImageHeight
                if width > maxImageWidth { // 竖图等比高存在宽越界的情形当横图处理
                    width = maxImageWidth
                    height = maxImageWidth * (imageHeight / imageWidth)
                }
            }
        }
        return CGSize(width: max(minClipSize.width, width), height: max(minClipSize.height, height))
    }
    
    private lazy var maxImageWidth: CGFloat = {
        return screenWidth - self.leftMargin * 2
    }()
    
    lazy var maxImageHeight: CGFloat = {
        return screenHeight - topMargin - safeAreaInsets.top - bottomViewHeight
    }()
    
    public init(image: UIImage, originBoxFrames: [CGRect]) {
        self.originalImage = image
        self.editImage = image
        self.originBoxFrames = originBoxFrames
        super.init(nibName: nil, bundle: nil)
        self.modalPresentationStyle = .fullScreen
    }
    
    required public init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    open override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    open override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        layoutUI()
        layoutInitialImage()
    }
    
    private let closeButton: UIButton = {
        let button = UIButton()
        button.setImage(UIImage(name: "icon_32px_close"), for: .normal)
        return button
    }()
    
    private let scrollView: UIScrollView = {
        let scrollView = UIScrollView()
        scrollView.backgroundColor = .black
        scrollView.minimumZoomScale = 1
        scrollView.maximumZoomScale = 5
        return scrollView
    }()
    
    private let containerView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.black
        return view
    }()
    
    private let shadowView: ClipImageBrowserShadowView = {
        let view = ClipImageBrowserShadowView()
        view.isUserInteractionEnabled = false
        view.backgroundColor = UIColor.black.withAlphaComponent(0.3)
        return view
    }()
    
    private lazy var imageView: UIImageView = {
        let view = UIImageView(image: self.editImage)
        view.contentMode = .scaleAspectFit
        view.clipsToBounds = true
        view.isUserInteractionEnabled = false
        return view
    }()
    
    private let bottomToolView = UIView()
    
    private let overlayView: ClipImageClipOverlayView = {
        let view = ClipImageClipOverlayView()
        view.isUserInteractionEnabled = false
        return view
    }()
}

// MARK: - 代理
extension ClipMoreImageController: UIScrollViewDelegate {
    
    public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return containerView
    }
    
    public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
        scrollView.setZoomScale(scale, animated: false)
    }
    
    public func scrollViewDidZoom(_ scrollView: UIScrollView) {
        let offSetX = scrollView.bounds.size.width > scrollView.contentSize.width ? (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0
        let offSetY = scrollView.bounds.size.height > scrollView.contentSize.height ? (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0
        containerView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offSetX, y: scrollView.contentSize.height * 0.5 + offSetY)
    }
}
// MARK: - 手势处理
extension ClipMoreImageController {
    @objc private func panClipView(_ pan: UIPanGestureRecognizer) {
        let point = pan.location(in: pan.view)
        if pan.state == .began {
            beginPanPoint = point
            clipOriginFrame = clipBoxFrame
            panEdge = calculatePanEdge(at: point)
        } else if pan.state == .changed {
            guard panEdge != .none else { return }
            updateClipBoxFrame(point: point)
        } else if pan.state == .cancelled || pan.state == .ended {
            self.panEdge = .none
        }
    }
    
    private func calculatePanEdge(at point: CGPoint) -> ClipImageBrowserController.ClipPanEdge {
        let frame = clipBoxFrame.insetBy(dx: -insetWidth, dy: -insetWidth)
        let cornerSize = CGSize(width: insetWidth * 2, height: insetWidth * 2)
        let topLeftRect = CGRect(origin: frame.origin, size: cornerSize)
        if topLeftRect.contains(point) {
            return .topLeft
        }
        
        let topRightRect = CGRect(origin: CGPoint(x: frame.maxX - cornerSize.width, y: frame.minY), size: cornerSize)
        if topRightRect.contains(point) {
            return .topRight
        }
        
        let bottomLeftRect = CGRect(origin: CGPoint(x: frame.minX, y: frame.maxY - cornerSize.height), size: cornerSize)
        if bottomLeftRect.contains(point) {
            return .bottomLeft
        }
        
        let bottomRightRect = CGRect(origin: CGPoint(x: frame.maxX - cornerSize.width, y: frame.maxY - cornerSize.height), size: cornerSize)
        if bottomRightRect.contains(point) {
            return .bottomRight
        }
        
        let topRect = CGRect(origin: CGPoint(x: frame.minX + cornerSize.width, y: frame.minY),
                             size: CGSize(width: frame.width - 2 * cornerSize.width, height: cornerSize.height + insetWidth))
        if topRect.contains(point) {
            return .top
        }
        
        let bottomRect = CGRect(origin: CGPoint(x: frame.minX + cornerSize.width, y: frame.maxY - cornerSize.height),
                                size: CGSize(width: frame.width - 2 * cornerSize.width, height: cornerSize.height + insetWidth))
        if bottomRect.contains(point) {
            return .bottom
        }
        
        let leftRect = CGRect(origin: CGPoint(x: frame.minX, y: frame.minY + cornerSize.height),
                              size: CGSize(width: cornerSize.width * 2, height: frame.height - 2 * cornerSize.height))
        if leftRect.contains(point) {
            return .left
        }
        
        let rightRect = CGRect(origin: CGPoint(x: frame.maxX - cornerSize.width * 2, y: frame.minY + cornerSize.height),
                               size: CGSize(width: cornerSize.width * 2, height: frame.height - 2 * cornerSize.height))
        if rightRect.contains(point) {
            return .right
        }
        
        let centerRect = CGRect(origin: CGPoint(x: frame.minX + cornerSize.width, y: frame.minY + cornerSize.height),
                                size: CGSize(width: frame.size.width - 2 * cornerSize.width, height: frame.size.height - 2 * cornerSize.height))
        if centerRect.contains(point) {
            return .center
        }
        return .none
    }
    
    private func updateClipBoxFrame(point: CGPoint) {
        var frame = clipBoxFrame
        let originFrame = clipOriginFrame
        
        let diffX = resetClipBoxOffset(point: point).offsetX
        let diffY = resetClipBoxOffset(point: point).offsetY
        
        switch panEdge {
        case .left:
            frame.origin.x = originFrame.minX + diffX
            frame.size.width = originFrame.width - diffX
        case .right:
            frame.size.width = originFrame.width + diffX
        case .top:
            frame.origin.y = originFrame.minY + diffY
            frame.size.height = originFrame.height - diffY
        case .bottom:
            frame.size.height = originFrame.height + diffY
        case .topLeft:
            frame.origin.x = originFrame.minX + diffX
            frame.size.width = originFrame.width - diffX
            frame.origin.y = originFrame.minY + diffY
            frame.size.height = originFrame.height - diffY
        case .topRight:
            frame.size.width = originFrame.width + diffX
            frame.origin.y = originFrame.minY + diffY
            frame.size.height = originFrame.height - diffY
        case .bottomLeft:
            frame.origin.x = originFrame.minX + diffX
            frame.size.width = originFrame.width - diffX
            frame.size.height = originFrame.height + diffY
        case .bottomRight:
            frame.size.width = originFrame.width + diffX
            frame.size.height = originFrame.height + diffY
        case .center:
            frame.origin.x = originFrame.minX + diffX
            frame.origin.y = originFrame.minY + diffY
        default:
            break
        }
        
        let minSize = CGSize(width: minClipSize.height, height: minClipSize.height)
        let maxSize = CGSize(width: imageView.frame.size.width, height: imageView.frame.size.height)
        let maxClipFrame = CGRect(origin: CGPoint(x: imageView.frame.minX, y: imageView.frame.minY), size: maxSize)
        
        frame.size.width = min(maxSize.width, max(minSize.width, frame.size.width))
        frame.size.height = min(maxSize.height, max(minSize.height, frame.size.height))
        
        let moveMaxX = maxClipFrame.maxX - frame.size.width
        let moveMaxY = maxClipFrame.maxY - frame.size.height
        
        let panMinX = min(maxClipFrame.maxX - minSize.width, max(frame.origin.x, maxClipFrame.minX))
        let panMinY = min(maxClipFrame.maxY - minSize.height, max(frame.origin.y, maxClipFrame.minY))
        
        frame.origin.x = min(moveMaxX, panMinX)
        frame.origin.y = min(moveMaxY, panMinY)
        
        if (panEdge == .topLeft || panEdge == .bottomLeft || panEdge == .left) &&
            frame.size.width <= minSize.width + CGFloat.ulpOfOne {
            frame.origin.x = originFrame.maxX - minSize.width
        }
        if (panEdge == .topLeft || panEdge == .topRight || panEdge == .top) &&
            frame.size.height <= minSize.height + CGFloat.ulpOfOne {
            frame.origin.y = originFrame.maxY - minSize.height
        }
        
        changeClipBoxFrame(newFrame: frame)
    }
    
    private func changeClipBoxFrame(newFrame: CGRect) {
        clipBoxFrame = newFrame
        overlayView.frame = newFrame
    }
    
    private func resetClipBoxOffset(point: CGPoint) -> (offsetX: CGFloat, offsetY: CGFloat) {
        var newPoint = point
        newPoint.x = max(imageView.frame.minX, newPoint.x)
        newPoint.y = max(imageView.frame.minY, newPoint.y)
        let diffX = ceil(newPoint.x - beginPanPoint.x)
        let diffY = ceil(newPoint.y - beginPanPoint.y)
        return (diffX, diffY)
    }
    
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        let point = gestureRecognizer.location(in: gestureRecognizer.view)
        return calculatePanEdge(at: point) != .none
    }
}
// MARK: - 裁剪相关
extension ClipMoreImageController {
    private func clipImageAction() -> (clipImage: UIImage, editRect: CGRect) {
        let frame = convertClipRectToEditImageRect()
        let origin = CGPoint(x: -frame.minX, y: -frame.minY)
        UIGraphicsBeginImageContextWithOptions(frame.size, false, editImage.scale)
        editImage.draw(at: origin)
        let temp = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        guard let cgi = temp?.cgImage else {
            return (editImage, CGRect(origin: .zero, size: editImage.size))
        }
        let newImage = UIImage(cgImage: cgi, scale: editImage.scale, orientation: .up)
        return (newImage, frame)
    }
    
    private func convertClipRectToEditImageRect() -> CGRect {
        let x = overlayView.frame.origin.x - imageView.frame.origin.x
        let y = overlayView.frame.origin.y - imageView.frame.origin.y
        let imageWidthScale = editImage.size.width / imageView.frame.size.width
        let imageHeightScale = editImage.size.height / imageView.frame.size.height
        
        let overViewWidth = overlayView.frame.width
        let overViewHeight = overlayView.frame.height
        
        var frame = CGRect.zero
        frame.origin.x = max(0, x * imageWidthScale)
        frame.origin.y = max(0, y * imageHeightScale)
        frame.size.width = overViewWidth * imageWidthScale
        frame.size.height = overViewHeight * imageHeightScale
        
        return frame
    }
}

extension ClipMoreImageController {
    @objc private func clickCloseButton() {
        closeController()
    }
    
    private func closeController(animated: Bool = true) {
        if let navigation = navigationController {
            navigation.popViewController(animated: animated)
        } else {
            dismiss(animated: true, completion: nil)
        }
    }
}

// MARK: - UI
extension ClipMoreImageController {
    private func setupUI() {
        edgesForExtendedLayout = []
        view.backgroundColor = UIColor.black
        view.addSubview(scrollView)
        scrollView.delegate = self
        //        view.addSubview(shadowView)
        scrollView.addSubview(containerView)
        containerView.addSubview(imageView)
        containerView.addSubview(overlayView)
        
        view.addSubview(bottomToolView)
        bottomToolView.backgroundColor = UIColor.green
        let sureTap = UITapGestureRecognizer(target: self, action: #selector(sureAction))
        bottomToolView.addGestureRecognizer(sureTap)
        
        view.addSubview(closeButton)
        closeButton.addTarget(self, action: #selector(clickCloseButton), for: .touchUpInside)
        let pan = UIPanGestureRecognizer(target: self, action: #selector(panClipView(_:)))
        pan.delegate = self
        containerView.addGestureRecognizer(pan)
        scrollView.panGestureRecognizer.require(toFail: pan)
    }
    
    private func layoutUI() {
        scrollView.frame = view.bounds
        containerView.frame = view.bounds
        shadowView.frame = view.bounds
        bottomToolView.frame = CGRect(x: 0, y: screenHeight - bottomViewHeight, width: screenWidth, height: bottomViewHeight)
        closeButton.frame = CGRect(x: 16, y: closeButtonTopMargin + safeAreaInsets.top, width: closeButtonSize.width, height: closeButtonSize.height)
        layoutInitialImage()
        overlayView.frame = CGRect(origin: .zero, size: clipBoxDefaultSize)
        overlayView.center = imageView.center
        changeClipBoxFrame(newFrame: overlayView.frame)
    }
    
    private func layoutInitialImage() {
        imageView.frame.size = previewSize
        var originY: CGFloat = topMargin + safeAreaInsets.top
        if previewSize.height < maxImageHeight {
            originY = topMargin + safeAreaInsets.top + (maxImageHeight - previewSize.height) / 2
        }
        imageView.frame.origin = CGPoint(x: (scrollView.frame.size.width - imageView.frame.size.width) / 2, y: originY)
    }
    
    @objc private func sureAction() {
        let image = clipImageAction().clipImage
        let temp = UIImageView(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
        temp.backgroundColor = UIColor.red
        temp.contentMode = .scaleToFill
        temp.image = image
        view.addSubview(temp)
    }
}

public class ClipBoxModel {
    public var boxFrame: CGRect = .zero
    public var id: Int = 0 // id
}
